"use strict";

import Flatten  from '../flatten';
const {Circle, Line, Point, Vector, Utils} = Flatten;
/**
 * Class Inversion represent operator of inversion in circle
 * //Inversion 是映射广义圆的欧几里得平面的变换
 * Inversion is a transformation of the Euclidean plane that maps generalized circles  (where line is considered as a circle with infinite radius) into generalized circles
 *  Inversion 是欧几里得平面的一种变换，它将广义圆（其中线被认为是具有无限半径的圆）映射为广义圆
 * See also https://en.wikipedia.org/wiki/Inversive_geometry and
 * http://mathworld.wolfram.com/Inversion.html <br/>
 * @type {Inversion}
 *
 * inversion 反转;倒置;倒转;颠倒
 */
export class Inversion {
    /**
     * Inversion constructor
     * @param {Circle} inversion_circle inversion circle
     */
    constructor(inversion_circle) {
        this.circle = inversion_circle;
    }


    get inversion_circle() {
        return this.circle;
    }

    static inversePoint(inversion_circle, point) {
        const v = new Vector(inversion_circle.pc, point);
        const k2 = inversion_circle.r * inversion_circle.r;
        const len2 = v.dot(v);
        const reflected_point = Utils.EQ_0(len2) ?
            new Point(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY) :
            inversion_circle.pc.translate(v.multiply(k2 / len2));
        return reflected_point;
    }

    static inverseCircle(inversion_circle, circle) {
        const dist = inversion_circle.pc.distanceTo(circle.pc)[0];
        if (Utils.EQ(dist, circle.r)) {     // Circle passing through inversion center mapped into line
            let d = (inversion_circle.r * inversion_circle.r) / (2 * circle.r);
            let v = new Vector(inversion_circle.pc, circle.pc);
            v = v.normalize();
            let pt = inversion_circle.pc.translate(v.multiply(d));

            return new Line(pt, v);
        } else {                           // Circle not passing through inversion center - map into another circle */
            /* Taken from http://mathworld.wolfram.com */
            let v = new Vector(inversion_circle.pc, circle.pc);
            let s = inversion_circle.r * inversion_circle.r / (v.dot(v) - circle.r * circle.r);
            let pc = inversion_circle.pc.translate(v.multiply(s));
            let r = Math.abs(s) * circle.r;

            return new Circle(pc, r);
        }
    }

    static inverseLine(inversion_circle, line) {
        const [dist, shortest_segment] = inversion_circle.pc.distanceTo(line);
        if (Utils.EQ_0(dist)) {            // Line passing through inversion center, is mapping to itself
            return line.clone();
        } else {                           // Line not passing through inversion center is mapping into circle
            let r = inversion_circle.r * inversion_circle.r / (2 * dist);
            let v = new Vector(inversion_circle.pc, shortest_segment.end);
            v = v.multiply(r / dist);
            return new Circle(inversion_circle.pc.translate(v), r);
        }
    }

    inverse(shape) {
        if (shape instanceof Point) {
            return Inversion.inversePoint(this.circle, shape);
        }
        else if (shape instanceof Circle) {
            return Inversion.inverseCircle(this.circle, shape);
        }
        else if (shape instanceof Line) {
            return Inversion.inverseLine(this.circle, shape);
        }
    }
};

Flatten.Inversion = Inversion;

/**
 * Shortcut to create inversion operator
 * @param circle
 * @returns {Inversion}
 */
export const inversion = (circle) => new Flatten.Inversion(circle);
Flatten.inversion = inversion;
